﻿#ifndef WIN32

#include "stack_trace_linux.hh"
#include "../box/service_box.hh"
#include "../box/system_exception.hh"
#include "../util/os_util.hh"
#include <cstdio>
#include <cstring>
#include <execinfo.h>
#include <iomanip>
#include <iostream>
#include <malloc.h>
#include <memory>
#include <signal.h>
#include <sstream>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

namespace kratos {
namespace util {

auto StackTrace::get_stack_frame_info(int max_frames) -> std::string {
  std::unique_ptr<void *> stack_frames(new void *[max_frames]);
  auto size = backtrace(stack_frames.get(), max_frames);
  if (!size) {
    return "";
  }
  auto **symbols = (char **)backtrace_symbols(stack_frames.get(), size);
  if (!symbols) {
    return "";
  }
  std::stringstream output_stream;
  output_stream << std::endl;
  // 获取栈地址对应的源代码信息，跳过最后两层栈
  auto depth = size;
  for (int i = 0; i < size; i++) {
    std::string symbol(symbols[i]);
    char line[1024] = {0};
    auto begin = symbol.find_first_of("[");
    auto end = symbol.find_last_of("]");
    // 取得地址
    std::string address = symbol.substr(begin + 1, end - begin - 1);
    std::stringstream ss;
    std::string binary = util::get_binary_name();
    ss << "addr2line " << address << " -i -e " << binary << " -f -C";
    // 取得地址对应的源代码信息
    FILE *pfp = popen(ss.str().c_str(), "r");
    output_stream << std::left << std::setw(2) << --depth << std::hex << " "
                  << address << " " << std::dec;
    int index = 0;
    while (fgets(line, sizeof(line) - 1, pfp)) {
      // 去掉换行
      if (line[strlen(line) - 1] == '\n') {
        line[strlen(line) - 1] = 0;
      }
      if (!index) {
        output_stream << line;
      } else {
        output_stream << " (" << line << ")";
      }
      index += 1;
    }
    output_stream << std::endl;
    // 关闭子进程输出
    if (pfp) {
      pclose(pfp);
    }
  }
  // 释放符号表
  if (symbols) {
    free(symbols);
  }
  return output_stream.str();
}

auto StackTrace::throw_system_exception(int sig) -> void {
  auto stack_info = get_stack_frame_info();
  switch (sig) {
  case SIGSEGV:
  case SIGBUS:
  case SIGILL:
    throw service::SegmentationFaultException(stack_info.c_str());
    break;
  case SIGSTKFLT:
    throw service::StackFaultException(stack_info.c_str());
    break;
  case SIGFPE:
    throw service::NumberException(stack_info.c_str());
    break;
  case SIGABRT:
    throw service::AbortException(stack_info.c_str());
    break;
  case SIGTRAP:
    throw service::DebugException(stack_info.c_str());
    break;
  default:
    break;
  }
  throw service::SystemException(stack_info.c_str());
}

sighandler_t segv_handler = nullptr;
sighandler_t stkflt_handler = nullptr;
sighandler_t fpe_handler = nullptr;
sighandler_t pipe_handler = nullptr;
sighandler_t abrt_handler = nullptr;
sighandler_t bus_handler = nullptr;
sighandler_t ill_handler = nullptr;
sighandler_t trap_handler = nullptr;

auto StackTrace::install_system_exception(kratos::service::ServiceBox *)
    -> bool {
  segv_handler = signal(SIGSEGV, &StackTrace::throw_system_exception);
  stkflt_handler = signal(SIGSTKFLT, &StackTrace::throw_system_exception);
  fpe_handler = signal(SIGFPE, &StackTrace::throw_system_exception);
  abrt_handler = signal(SIGABRT, &StackTrace::throw_system_exception);
  pipe_handler = signal(SIGPIPE, &StackTrace::throw_system_exception);
  bus_handler = signal(SIGBUS, &StackTrace::throw_system_exception);
  ill_handler = signal(SIGILL, &StackTrace::throw_system_exception);
  trap_handler = signal(SIGTRAP, &StackTrace::throw_system_exception);
  return true;
}

auto StackTrace::uninstall_system_exception() -> void {
  signal(SIGSEGV, segv_handler);
  signal(SIGSTKFLT, stkflt_handler);
  signal(SIGFPE, fpe_handler);
  signal(SIGPIPE, pipe_handler);
  signal(SIGABRT, abrt_handler);
  signal(SIGBUS, bus_handler);
  signal(SIGILL, ill_handler);
  signal(SIGTRAP, trap_handler);
}

} // namespace util
} // namespace kratos

#endif // WIN32
